#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <setjmp.h>
#include <stdarg.h>
#include <stdint.h>
#include "SAtest.h"

#define HAVE_SUBLIST 1
#define NO_SUBLIST 0

#if DEBUG
#define malloc(size)  malloc(size);printf("%s:c:%d  allocated\n", __FILE__,__LINE__)
#define free(ptr)  free(ptr);printf(SOURCE_FORMAT" freed \n", __FILE__,__LINE__)
#endif

/*global variable track*/
int _add_global_variable(const char* const name, void* ptr,const char * const file,const int line)
{
    GlobalVariableInfo * global_variable = malloc(sizeof(GlobalVariableInfo));
    global_variable->ptr = ptr;
    strcpy(global_variable->name, name);
    global_variable->value = *chptr2largest(global_variable->ptr);
    ListNode * nodeptr = create_list_node(global_variable, NO_SUBLIST, file,line);
    add_node_queuehead(&global_variable_list_headptr, nodeptr);
#if DEBUG
    printf("%s:%lld\n",global_variable->name,global_variable->value);
#endif
    return 0;
}

void update_global_variable()
{
    ListNode* currentptr = global_variable_list_headptr;
    GlobalVariableInfo* workptr = NULL;
    while(currentptr){
        workptr = (GlobalVariableInfo*)(currentptr->contents);
        workptr->value =*chptr2largest(workptr->ptr);
    }
}

int _check_global_variable_changed(const char * const file,const int line)  // whiteList is the global variables tha won't be check
{
    int if_change = 0;
    ListNode* currentptr = global_variable_list_headptr;
    GlobalVariableInfo* workptr = NULL;
    while(currentptr){
        workptr = (GlobalVariableInfo*)(currentptr->contents);
        if(workptr->value != *chptr2largest(workptr->ptr)){
            if(!if_in_whiteList(workptr->ptr,wlist.whiteListptr, wlist.num)){
                print_error(SOURCE_FORMAT"ERROR: global variable \"%s\" has been changed\n", file,line,workptr->name);
                if_change = 1;
            }
            else{
                printf("INFO:global variable \"%s\" is changed but it's in white list\n", workptr->name);
            }
        } 
        currentptr = currentptr->nextnode;
    }

    return if_change;
}

int if_in_whiteList(void * ptr, void* whitelistptr[], int len)
{
    int if_in = 0;
    for(len--;len>=0;len--){
        if(ptr == whitelistptr[len]){
            if_in = 1;break;
        }
    }
    return if_in;
}

void _create_whiteList(int len,...)
{
    if(wlist.whiteListptr)   //destroy the old wlist
        free(wlist.whiteListptr);

    void** whitelistptr = (void**)malloc((len*sizeof(void*)));
    if(!whitelistptr){
        print_error(SOURCE_FORMAT"ERROR:fail to allocated\n",__FILE__,__LINE__);
        exit(-1);
    }
    wlist.whiteListptr = whitelistptr;
    wlist.num = len;

    void** workptr = whitelistptr;
    va_list var_ptr;
    va_start(var_ptr,len);
    while(len>0){
        len--;
        workptr = whitelistptr+len;
        *(workptr) = va_arg(var_ptr,void*);
    }
    va_end(var_ptr);
    printf("%x\t",&wlist);
    return ;
}
/*after one test, destroy the global variable list*/
void destroy_global_variable_list()
{
    free_list(&global_variable_list_headptr);
}

/*these are assert functions that used to compare values whether are you want*/
int _assert_ture(const char * const expression, const LargestType result, const char * const file,const int line)
{
    if(result)
        return 1;
    else
    {
        print_error(SOURCE_FORMAT"ERROR:expression \"%s\" not ture\n", file, line, expression);
        return 0;
    }
    
}
                    
int _assert_int_equal(LargestType a, LargestType b,const char * const file,const int line)
{
    if(a!=b)
    {
        print_error(SOURCE_FORMAT"ERROR: %lld!=%lld\n",file , line, a, b);
        return 0;
    }    
    return 1;
    
}

int _assert_string_equal(const char * const a, const char * const b, const char * const file,const int line )
{
    if(strcmp(a,b))
    {
        print_error(SOURCE_FORMAT"ERROR: \"%s\"!=\"%s\"\n", file, line, a, b);
        return 0;
    }
    return 1;
}

/*check whether two parts of memory are equal*/
int _assert_memory_equal(const char * const a, const char * const b,const int size, const char * const file,const int line)
{
    if(size>0){
        char x,y;
        int i = 1;
        for(x=*a,y=*b; i<=size; i++){
            if(x!=y){
                print_error(SOURCE_FORMAT"ERROR: %x != %x\n", file, line ,a, b);
                return 0;
            }
            x = *(a+i);
            y = *(b+i);
        }
    }
    else{
        print_error(SOURCE_FORMAT"ERROR: size invalid\n",file,line);
        return 0;
    }
    

}

int _assert_value_arrange(const LargestType value, const LargestType min, const LargestType max, const char * const file,const int line )
{
    if(min>max)
    {
        print_error(SOURCE_FORMAT"ERROR: min>max\n",file,line);
        return 0;
    }
    if(value>=min && value<=max){
        return 1;
    }
    else if(value<min){
        print_error(SOURCE_FORMAT"ERROR: %lld(value) < %lld(min)\n",file,line,value,min);
        return 0;
    }
    else
    {
        print_error(SOURCE_FORMAT"ERROR: %lld(value) > %lld(max)\n",file,line,value,max);
        return 0;
    }
    
    
}

/*list operation*/
ListNode * create_list_node(void* contents, const int8_t if_sublist,const char * const file,const int line)
{
    ListNode * newNode = (ListNode*)malloc(sizeof(ListNode));
    if(newNode){
        newNode->contents = contents;
        newNode->if_sublist = if_sublist;
        newNode->nextnode = NULL;
        return newNode;
    }
    else{
        print_error(SOURCE_FORMAT"ERROR: fail to create a node!\n",file,line);
        return NULL;
    }
}

void add_node_queuehead(ListNode ** headptr, ListNode * node)
{
    if(!*headptr){          //empty queue
        *headptr = node;
        return ;
    }
    else{
        node->nextnode = *headptr;
        *headptr = node;
    }
    return ;
}
/*recursively free all room of the headptr, including its sublists*/
void free_list(ListNode ** headptr)           
{
    if(!(*headptr)){
        return ;
    }
    else{
        ListNode * currentptr = *headptr;
        ListNode * preptr = NULL;
        while(currentptr!=NULL)
        {
            if(currentptr->if_sublist)
                free_list(&(currentptr->contents))  ; // this node have sublist, first free its sublist
            free(currentptr->contents);
            preptr = currentptr;
            currentptr = currentptr->nextnode;
            free(preptr);
            #if DEBUG
                printf("%x freed\n", preptr);
            #endif
        }   
        *headptr = NULL;
        return ;
    }
    
}

/*remove a node with all its sublists*/
void remove_node(ListNode ** headptr, ListNode * preNode, ListNode * node)
{
    if(*headptr){
        if(node==*headptr){
            *headptr = node->nextnode;
            if(node->if_sublist)
                free_list(&(node->contents));
            free(node->contents);
            free(node);
        }
        else{
            preNode->nextnode = node->nextnode;
            if(node->if_sublist)
                free_list(&(node->contents));
            free(node->contents);
            free(node);
        }
    }    
}
/*get the last node*/
ListNode* tail_node(ListNode ** preNode, ListNode * headptr)
{
    ListNode * curptr = headptr;
    ListNode * preptr = NULL;
    if(curptr){
        while(curptr->nextnode){
        preptr = curptr;
        curptr = curptr->nextnode; 
        }
    }
    if(preNode)
        *preNode = preptr;
    return curptr;
}

ListNode* find_function_node(ListNode ** preNode, ListNode * headptr, const char* const tag)
{
    ListNode* curptr = headptr;
    ListNode* preptr = NULL;
    //assert_true(tag);
    while(curptr){
        #if DEBUG
            printf("%s/%s\n",tag,((ListNode*)(curptr->contents))->contents);
        #endif
        if(!strcmp(((ListNode*)(curptr->contents))->contents,tag)){
            if(preNode)
                *preNode = preptr;
            return curptr;
        }
        preptr = curptr;
        curptr = curptr->nextnode;
    }
    #if DEBUG
    print_error(SOURCE_FORMAT"INFO:fail to find the node\n");
    #endif
    if(preNode)
        *preNode = NULL;
    return NULL;
}

/*it's a develped find method, you can use it as
find_recursively(0,rootptr,2,STRING_ROLE,find_function_node,"aaa",INT_ROLE,find_value_node,10)
but be careful that this usage should be unstable, and you need to only use it here with the correct format*/
ListNode* find_recursively(ListNode ** pre,ListNode * root,int loop_num,...)
{
    va_list arg_ptr;
    int rule_type = 0;
    ListNode * target = root;
    ListNode * preptr = NULL;
    findNode_String_Rule find_node_string;
    // findNode_Int_Rule find_node_int;
    // findNode_ptr_Rule find_node_ptr;
    // findNode_mem_Rule find_node_mem;
    
    va_start(arg_ptr,loop_num);
    for(;loop_num;loop_num--){
        rule_type = va_arg(arg_ptr,int);
        switch(rule_type){
            case STRING_RULE:
                find_node_string = va_arg(arg_ptr,findNode_String_Rule);
                target= find_node_string(&preptr, target,va_arg(arg_ptr,char*));
                break;
            case INT_RULE:
            case PTR_RULE:
            case MEM_RULE:
            default:
                break;
        }
        preptr = target;
        if(target){
            target = target->contents;
            target = target->nextnode;
        }
    }
    va_end(arg_ptr);
    if(pre){
        *pre = preptr;
    }
    return target;

}

/*check memory allocated and deallocated / memory leak check*/
void* _sa_malloc(size_t size,const char * const file, const int line)
{
    void* ptr = malloc(size);
    add_ptr_to_track(ptr, file, line);

    return ptr;
}
//add  a ptr to track linklist
void add_ptr_to_track(void* ptr,const char * const file, const int line )
{
    Block * block = malloc(sizeof(Block));
    block->ptr = ptr;
    block->file = malloc(sizeof(file));
    strcmp(block->file,file);
    block->line = line;
    ListNode* node = create_list_node(block, NO_SUBLIST, file, line);
    add_node_queuehead(&block_info_list_headptr, node);
}

void _sa_free(void* ptr, const char* const file, const int line)
{
    ListNode * currentptr = block_info_list_headptr;
    ListNode * preptr = NULL;
    Block * workptr;
    while(currentptr){
        workptr = (Block*)(currentptr->contents);
        if(workptr->ptr == ptr){           // find the ptr
            free(ptr);
            if(!preptr){       // first node
                block_info_list_headptr = currentptr->nextnode;
            }
            else{
                preptr->nextnode = currentptr->nextnode;
            }
            preptr = currentptr;
            currentptr = currentptr->nextnode;
            char * f = ((Block*)(preptr->contents))->file;
            free(f);
            free(preptr->contents);
            free(preptr);
            return ;
        }
        else{
            preptr = currentptr;
            currentptr = currentptr->nextnode;
        }
    }
    print_error(SOURCE_FORMAT"ERROR: pointer invalid, free failed!\n", file,line);
}

void* _sa_realloc(void* ptr, size_t size, const char* const file, const int line)
{
    void* newptr = realloc(ptr,size);
    sa_free(ptr);
    add_ptr_to_track(newptr,file,line);
    return newptr;
}

void _check_memory_leak()
{
    ListNode * curptr = block_info_list_headptr;
    while(curptr){
        print_error(SOURCE_FORMAT"ERROR: memory leak! you never free the memory of addr:%x\n",
            ((Block*)(curptr->contents))->file,((Block*)(curptr->contents))->line,((Block*)(curptr->contents))->ptr );
        curptr = curptr->nextnode;
    }
    free_list(&block_info_list_headptr);
}

/*mock functions*/
void _will_return(const char* const function, const LargestType value, const char* const file, const int line)
{
    ListNode * curptr = find_function_node(0,mock_function_return_list_headptr,function);
    ListNode * subList = NULL;
    if(!curptr)  // first add
    {
        char * func = malloc(sizeof(function));
        strcpy(func,function);
        subList = create_list_node(func, NO_SUBLIST,file,line);
        curptr = create_list_node(subList, HAVE_SUBLIST, file, line);
        add_node_queuehead(&mock_function_return_list_headptr,curptr);
    }
    else{
        subList = curptr->contents;
    }
    mockFNode* node = malloc(sizeof(mockFNode));
    node->line = line;
    node->value = value;
    curptr = create_list_node(node, NO_SUBLIST, file, line);
    add_node_queuehead(&(subList->nextnode), curptr);
}

LargestType _mock(const char* const function,const char* const file, const line )
{
    ListNode * preptr = NULL;
    ListNode * curptr = find_function_node(&preptr, mock_function_return_list_headptr, function);
    if(curptr){
        ListNode * workptr = (ListNode*)(curptr->contents);
        ListNode * preworkptr = NULL;
        workptr = tail_node(&preworkptr, workptr);
        LargestType result = ((mockFNode*)(workptr->contents))->value;
        if(preworkptr==(ListNode*)(curptr->contents)){
            remove_node(&mock_function_return_list_headptr, preptr,curptr);
            return result;
        }
        else{
            curptr=curptr->contents;
            remove_node(&(curptr->nextnode),preworkptr,workptr);
            return result;
        }
    }
    else
    {
        print_error(SOURCE_FORMAT"ERROR: mock()failed, you don't add a value to will_return\n",file,line);
        return 0;
    }
    
}
/*check if there are rest values in the list*/
void check_return_value_list()
{
    ListNode * curptr = mock_function_return_list_headptr;
    ListNode * workptr = NULL;
    mockFNode * node = NULL;
    while(curptr){
        workptr = curptr->contents;
        while(workptr){
            workptr = workptr->nextnode;
            if(workptr){
                node = workptr->contents;
                print_error(SOURCE_FORMAT"ERROR: will_return %lld, but never return from mock()\n","test",node->line,node->value);
            }
        }
        curptr = curptr->nextnode;
    }
    free_list(&mock_function_return_list_headptr);
}

/*expect check functions*/
void _expect_value(const char * const function, const char* const parameter,const LargestType value, 
                            const char * const file, const int line)
{
    // ListNode* curptr = find_function_node(0,mock_function_parameter_list_headptr,function); // work at function chain
    // ListNode* paraptr = NULL;                                               // work at parameter chain
    // ListNode* vaptr = NULL;                                     //work at value chain
    // char * f;
    // //find the function node
    // if(!curptr){       //new node
    //     f = malloc(sizeof(function));
    //     strcpy(f,function);
    //     paraptr = create_list_node(f, NO_SUBLIST, file,line);
    //     curptr = create_list_node(paraptr, HAVE_SUBLIST,file,line);
    //     add_node_queuehead(&(mock_function_parameter_list_headptr),curptr);
    // }
    // else{
    //     paraptr = curptr->contents;
    // }
    // //find the parameter node
    // paraptr = find_function_node(0,paraptr->nextnode,parameter);
    // if (!paraptr){
    //     f = malloc(sizeof(parameter));
    //     strcpy(f,parameter);
    //     ListNode * vaptr = create_list_node(f,NO_SUBLIST,file,line);
    //     paraptr = create_list_node(vaptr, HAVE_SUBLIST, file, line);
    //     curptr = curptr->contents;
    //     add_node_queuehead(&(curptr->nextnode),paraptr);
    // }
    // else{
    //     vaptr = paraptr->nextnode;
    // }
    //add node
    ListNode* paraptr = get_paraptr_node(function,parameter,file,line);
    mockFNode * node = malloc(sizeof(mockFNode));
    node->line = line;
    node->value = value;
    paraptr = paraptr->contents;
    ListNode* vaptr = create_list_node(node, NO_SUBLIST, file, line);
    add_node_queuehead(&(paraptr->nextnode),vaptr);
}

void _expect_string(const char * const function, const char* const parameter,const char* const String, 
                            const char * const file, const int line)
{
    ListNode * paraptr= get_paraptr_node(function,parameter,file,line);
    mockFNode* node = malloc(sizeof(mockFNode));
    node->line = line;
    char * str = malloc(sizeof(String));
    strcpy(str,String);
    node->value = chtype2largest(str);
    paraptr = paraptr->contents;
    ListNode* vaptr = create_list_node(node,NO_SUBLIST, file,line);
    add_node_queuehead(&(paraptr->nextnode),vaptr);
}

void _check_expect(const char * const function, const char * const parameter,LargestType value,const char * const file, const int line)
{
    ListNode * node = NULL;
    ListNode * prenode = NULL;
    ListNode * paranode = NULL;
    node = find_recursively(&prenode,mock_function_parameter_list_headptr,2,
    STRING_RULE,find_function_node,function,STRING_RULE,find_function_node,parameter);
    paranode = prenode; // get the node at the parameter chain and link to the value chain.
    node = tail_node(&prenode,node); 

    if(!node){
        print_error(SOURCE_FORMAT"ERROR: parameter '%s' not found! please check\n", parameter,file,line);
        return;
    }
    LargestType tag = ((mockFNode*)(node->contents))->value;
    if(value!=tag){
        print_error(SOURCE_FORMAT"ERROR: in function: %s, %s's real value is %lld, not your expected:%lld  you expect it at c:%d\n",
                        file, line, function,parameter,value,tag, ((mockFNode*)(node->contents))->line);
    }
    if(!prenode){
        free_list(&(paranode->contents));
    }
    else{
        prenode->nextnode = NULL;
        free(node->contents);
        free(node);
    }
    #if DEBUG
        printf("value:%lld tag:%lld\n",value,tag);
    #endif
}

ListNode * get_paraptr_node(const char * const function, const char* const parameter, const char * const file, const int line)
{
    ListNode* curptr = find_function_node(0,mock_function_parameter_list_headptr,function); // work at function chain
    ListNode* paraptr = NULL;                                               // work at parameter chain
    ListNode* vaptr = NULL;                                     //work at value chain
    char * f;
    //find the function node
    if(!curptr){       //new node
        f = malloc(sizeof(function));
        strcpy(f,function);
        paraptr = create_list_node(f, NO_SUBLIST, file,line);
        curptr = create_list_node(paraptr, HAVE_SUBLIST,file,line);
        add_node_queuehead(&(mock_function_parameter_list_headptr),curptr);
    }
    else{
        paraptr = curptr->contents;
    }
    //find the parameter node
    paraptr = find_function_node(0,paraptr->nextnode,parameter);
    if (!paraptr){
        f = malloc(sizeof(parameter));
        strcpy(f,parameter);
        ListNode * vaptr = create_list_node(f,NO_SUBLIST,file,line);
        paraptr = create_list_node(vaptr, HAVE_SUBLIST, file, line);
        curptr = curptr->contents;
        add_node_queuehead(&(curptr->nextnode),paraptr);
    }
    return paraptr;
}

void _check_expect_s(const char * const function, const char * const parameter,const char* str,const char * const file, const int line)
{
    ListNode * node = NULL;
    ListNode * prenode = NULL;
    ListNode * paranode = NULL;
    node = find_recursively(&prenode,mock_function_parameter_list_headptr,2,
    STRING_RULE,find_function_node,function,STRING_RULE,find_function_node,parameter);
    paranode = prenode; // get the node at the parameter chain and link to the value chain.
    node = tail_node(&prenode,node); 

    if(!node){
        print_error(SOURCE_FORMAT"ERROR: parameter '%s' not found! please check\n", parameter,file,line);
        return;
    }
    char* tag = (char *)(((mockFNode*)(node->contents))->value);
    
    #if DEBUG
         printf("str:%s  tag:%s\n", str,tag);
    #endif
    if(strcmp(tag,str)){
        print_error(SOURCE_FORMAT"ERROR: in function: %s, %s's real value is %s, not your expected:%s  you expect it at c:%d\n",
                       file, line, function,parameter,str,tag, ((mockFNode*)(node->contents))->line);
    //free(tag);
    }
    if(!prenode){
        free_list(&(paranode->contents));
    }
    else{
        prenode->nextnode = NULL;
        free(node->contents);
        free(node);
    }
    #if DEBUG
        printf("value:%s tag:%s\n",str,tag);
    #endif
}

/*run test functions*/
void _run_test(const char * const test_name, runtestFunction function, int8_t mode)
{
    //initialize 
    free_list(&global_variable_list_headptr);
    free_list(&mock_function_parameter_list_headptr);
    free_list(&mock_function_return_list_headptr);
    free_list(&block_info_list_headptr);
    memset(&wlist,0,sizeof(WhiteList));
    //run function
    printf("\n***%s test start***\n",test_name);
    function(mode);
    //check 
    _check_memory_leak();
    check_return_value_list();
    printf("\n***%s test end***\n",test_name);
}

void _run_tests(UnitTest tests[], int test_number)
{
    int i;
    for(i = 0; i< test_number; i++)
    {
        _run_test(tests[i].testname,tests[i].testfunction,tests[i].testmode);
    }

}

